#pragma once
//所有协议的读取分析处理等操作都是通过这个来完成
#include<iostream>
#include<sstream>
#include<unistd.h>
#include<string>
#include<vector>
#include <algorithm>
#include <unordered_map>

#include <sys/types.h>
#include <sys/stat.h>
#include<fcntl.h>
#include<sys/socket.h>
#include<sys/sendfile.h>
#include<sys/wait.h>
#include "Log.hpp"
#include "Util.hpp"

#define SEP ": "
#define WEB_ROOT "wwwroot"
#define HOME_PAGE "index.html"
#define HTTP_VERSION "HTTP/1.0"
#define LINE_END "\r\n"
#define PAGE_400 "400.html"
#define PAGE_404 "404.html"
#define PAGE_500 "500.html"

//状态码
#define OK 200
#define BAD_REQUEST 400
#define NOT_FOUND 404
#define SERVER_ERROR 500

class Request
{
public:
    std::string request_line; //请求行
    std::vector<std::string> request_header; //包括请求报头各字段
    std::string blank; //表示空行
    std::string request_body; //请求正文

    //下面是保存解析完毕之后的结果，包括：http方法，URI，http版本等
    std::string method; //请求方法
    std::string uri; //URL也分成两部分，左边是URL，右边可能是带的参数
    std::string version; //http版本

    std::unordered_map<std::string, std::string> header_kv; //将报头中的字段以KV形式解析出来
    int content_length; //报头字段中表示正文的长度，单位字节
    std::string path; //表示URL的路径
    std::string query_string; //表示URL右边带的参数
    std::string suffix; //表示文件后缀，方便后面填写响应报文的Content-Tyoe

    bool cgi; //表示是否要使用CGI模式
    int size;
public:
    Request()
        : content_length(0) //请求长度初始化为0
        , cgi(false) //默认不使用CGI
    {}
    ~Request(){}
};

class Response
{
public:
    std::string status_line; //状态行
    std::vector<std::string> response_header; //响应报头的各个属性
    std::string blank; //空行
    std::string response_body; //响应正文

    int status_code; //表示响应报文中第一行请求行的状态码
    int fd; //表示用open打开的文件的文件描述符
    int size; //表示响应文件的大小
    std::string suffix; //表示响应文件的后缀
public:
    Response()
        : status_code(OK) //状态码默认200
        , fd(-1)
        , blank(LINE_END) //设置空行
        , size(0)
    {}
    ~Response(){}
};

static std::string Code2Desc(int code)
{
    std::string desc;
    switch(code){
        case 200:
            desc = "OK";
            break;
        case 404:
            desc = "Not Found";
            break;
        case 500:
            desc = "Internal Server Error";
            break;
        default:
            break;
    }
    return desc;
}

static std::string SuffixToDesc(const std::string &suffix)
{
    static std::unordered_map<std::string, std::string> suffix2desc = {
        { ".html", "text/html" },
        { ".css", "text/css" },
        { ".js", "application/javascript" },
        { ".jpg", "application/x-jpg" },
        { ".xml", "application/xml" },
        { ".png", "image/png" },
    };
    //https://www.runoob.com/http/http-content-type.html
    auto iter = suffix2desc.find(suffix);
    if(iter != suffix2desc.end()) return iter->second;
    else return "text/html";
}

//这个类用来完成业务的具体逻辑 —— 读取分析请求，构建响应，IO通信
class EndPoint
{
private:
    //读取并解析报头部分方法
    bool RecvRequestLine() //读取请求报头的第一行请求行
    {
        std::string& line = http_request.request_line;
        if(Util::ReadLine(_sock, line) > 0)
        {
            line.resize(line.size() - 1);
            LOG(INFO, http_request.request_line);
        }
        else
        {
            stop = true; //如果读取出错，直接不玩了
        }
        return stop;
    }
    bool RecvRequestHeader() //读取报头各字段
    {
        std::string line;
        while(true) //当请求报文中出现单个\n时，就说明请求报头读取完毕
        {
            line.clear(); //每次读取前刷新一下
            //http请求报头的各字段都是以\n结尾的，所以可以用作分割符，将各字段分离出来
            if(Util::ReadLine(_sock, line) <= 0) 
            {
                stop = true; //如果读取报头出错，我也直接不玩了
                break;
            }
            if(line == "\n") //这个就表示读取到的这一行只有一个单独的换行符，就表示读取到空行了
            {
                http_request.blank = line; //空行后面的就是请求正文
                break;
            }
            line.resize(line.size() - 1); //ReadLine读取时是会把\n一起搞上来的，但是我们不需要\n，所以要去掉
            http_request.request_header.push_back(line); //然后将各字段信息插入到请求类中，方便后续处理
            LOG(INFO, line);
        }   
        return stop;
    }
    void ParseRequestLine() //把请求行变成三个字符串后存起来
    {
        auto &line = http_request.request_line; //拿到请求行
        std::stringstream ss(line); //stringstring可以将目标字符传以流的形式格式化输入到目标字符串里
        ss >> http_request.method >> http_request.uri >> http_request.version;

        //因为可能不是所有的协议都严格按照标准的，所以方法可能是 "Get"，或者"get"，就不是全是大写，所以我们需要做下处理
        auto &method = http_request.method;
        std::transform(method.begin(), method.end(), method.begin(), ::toupper); //toupper就是全转成大写，类似仿函数
    }
    void ParseRequestHeader() //把请求报头各字段变成一个一个的键值对然后存起来
    {
        std::string key;
        std::string value;
        for(auto &iter : http_request.request_header)
        {
            //以SEP为分隔符，把字符从iter迭代器拿出来，通过这个函数做分割放到key和value里面
            if(Util::CutString(iter, key, value, SEP)) 
            {
                http_request.header_kv.insert(make_pair(key, value)); //然后再以KV形式将key和value保存到map里
                // std::cout << "debug: " << key << std::endl;
                // std::cout << "debug: " << value << std::endl;
            }
        }
    }
    bool IsNeedRecvHttpRequestBody() //判断是不是POST方法
    {
        auto& method = http_request.method;
        if(method == "POST")
        {
            auto& header_kv = http_request.header_kv;
            //header_kv["Content-Length"] //并不是所有的协议都按照规定的，所以不建议这样写
            auto iter = header_kv.find("Content-Length"); //找该属性
            if(iter != header_kv.end()) //找到了该属性
            {
                LOG(INFO, "Post Method, Content-Length: "+iter->second);
                http_request.content_length = atoi(iter->second.c_str());
                return true;
            }
        }
        return false;
    }
    bool RecvRequestBoby() //读取并保存请求正文
    {
        if(IsNeedRecvHttpRequestBody()) //如果method是"POST"，就要读取正文内容
        {
            int content_length = http_request.content_length; //正文长度
            auto &body = http_request.request_body; //要存储正文的地方

            char ch = 0;
            while(content_length)
            {
                ssize_t s = recv(_sock, &ch, 1, 0);
                if(s > 0)
                {
                    body.push_back(ch);
                    content_length--;
                }
                else
                {
                    stop = true; //正文读取出错，我直接不玩了
                    break;
                }
            }
            LOG(INFO, body);
        }
        return stop;
    }
private:
    int ProcessCgi()
    {
        //问题
        //1，父进程拿到的数据在哪里？body(POST), query_string(GET)
        //2，父进程如何把参数交给子进程？管道，环境变量(具有全局属性，可以被子进程继承下去，并不受exec的影响)

        int code = OK; //默认状态码是200
        auto &method = http_request.method;             // 请求方法
        //获取要传递的参数
        auto &query_string = http_request.query_string; // GET
        auto &body_text = http_request.request_body;    // POST
        auto &bin = http_request.path;                  // 要让子进程执行的目标程序，走到了这里说明一定存在
        int content_length = http_request.content_length; //请求正文的长度
        auto &response_body = http_response.response_body; //负责保存CGI程序的处理结果

        // 用于导入环境变量
        std::string query_string_env;
        std::string method_env;
        std::string content_length_env;

        // 对于程序替换：
        // 1，可以创建子进程，让子进程去执行exec进程替换，那么要替换的目标程序在哪里呢？是通过URL告诉我们的
        // 2，子进程处理完通信要把数据发给父进程，所以要用到进程间通信，也就是管道；同时父子进程要相互通信，那就要两个管道

        // 约定：管道的读写，完全站在父进程角度
        int input[2];
        int output[2];
        if (pipe(input) < 0) // pipi创建管道，可能会失败
        {
            LOG(ERROR, "pipe input error");
            code = SERVER_ERROR;
            return code;
        }
        if (pipe(output) < 0)
        {
            LOG(ERROR, "pipe output error");
            code = SERVER_ERROR;
            return code;
        }

        pid_t pid = fork();
        if (pid == 0) // 子进程，需要关闭两个管道的读写端
        {
            close(input[0]);  // 一般0为读，1为写，所以子进程通过input从父进程读数据
            close(output[1]); // 通过output向父进程写数据

            method_env = "METHOD=";
            method_env += method;
            putenv((char *)method_env.c_str()); // putenv导入环境变量，导入请求方法

            //根据不同的方法导入环境变量
            if (method == "GET")
            {
                query_string_env = "QUERY_STRING=";
                query_string_env += query_string;
                putenv((char *)query_string_env.c_str());
                LOG(INFO, "Get Method, Add Query_String Env");
            }
            else if (method == "POST")
            {
                // 通过环境变量告诉cgi程序要从管道读取多少正文数据
                //由于正文可能很长，所以正文内容通过管道传递
                content_length_env = "CONTENT_LENGTH=";
                content_length_env += std::to_string(content_length);
                putenv((char *)content_length_env.c_str());
                LOG(INFO, "Post Method, Add Content_Length Env");
            }
            else {} //要想支持其它方法直接在这里扩展即可
            
            // 替换成功之后，目标子进程如何得知对应的读写文件描述符是多少呢?
            // 程序替换，只替换代码和数据，不会对内核进程的数据结构做改变，所以替换后，位于用户层的文件描述符没有了，但是曾经打开的文件的数据结构还在
            // 约定：让目标进程被替换之后，读取管道等价于读取标准输入；写入管道等价与写到标准输出
            dup2(output[0], 0); // 让本来从0标准输入读的，现在直接从管道里面读
            dup2(input[1], 1);  // 让本来往1标准输出写的，我现在直接让它写到管道里

            execl(bin.c_str(), bin.c_str(), nullptr); // 子进程进行程序替换
            exit(1);
        }
        else if (pid < 0) // 创建子进程失败
        {
            LOG(ERROR, "fork error! ");
            return 404;
        }
        else // 父进程
        {
            close(input[1]);  // 一般0为读，1为写，所以父进程通过output向子进程写数据
            close(output[0]); // 通过input读数据
            if (method == "POST") //如果请求方法是POST，就要把正文部分通过管道传递给CGI程序
            {
                const char *start = body_text.c_str();
                int total = 0; // 表示已经写了多少
                int size = 0;  // 表示这一次要写多少
                while (total < content_length && (size = write(output[1], start + total, body_text.size() - total)) > 0)
                {
                    total += size;
                }
            }
            // 子进程处理完数据后通过管道写回来，我们也通过管道读取到
            char ch = 0;
            while (read(input[0], &ch, 1) > 0) //读取结果
            {
                // CGI执行完之后，把结果放到响应的正文里，不能直接send发回，因为这部分内容只是响应的正文，不是响应全部
                response_body.push_back(ch);
            }
            int status = 0; // 子进程退出码
            pid_t ret = waitpid(pid, &status, 0);
            if (ret == pid)
            {
                if (WIFEXITED(status)) // 这个宏用来检测进程退出码是否正常
                {
                    if (WEXITSTATUS(status) == 0) code = OK; // 检测进程退出码是否为0，如果是0说明一切正常
                    else code = BAD_REQUEST;
                }
                else
                {
                    code = SERVER_ERROR;
                }
            }
            // 关闭不必要的文件描述符，最大程度节省资源
            close(input[0]);
            close(output[1]);
        }
        return code;
    }
    int ProcessNonCgi()
    {
        http_response.fd = open(http_request.path.c_str(), O_RDONLY); // 以只读打开指定路径的文件
        if (http_response.fd >= 0)                                    // 下面的添加状态行的操作都建立在文件被成功打开的情况下
        {
            // 这里只要打开成功就可以了，返回静态网页直接交给错误码处理即可
            LOG(INFO, http_request.path + " open success!");
            return OK;
        }
        return NOT_FOUND;
    }
    void BuildOkResponse()
    {
        std::string line = "Content-Length: ";
        if (http_request.cgi) // POST方法和带参GET方法
        {
            line += std::to_string(http_response.response_body.size());
        }
        else // 无参GET方法
        {
            line += std::to_string(http_request.size);
        }
        line += LINE_END;
        http_response.response_header.push_back(line);

        line = "Content-Type: ";
        line += SuffixToDesc(http_request.suffix); // 添加资源文件类型
        line += LINE_END;
        http_response.response_header.push_back(line);
    }
    void HandlerError(std::string page)
    {
        http_request.cgi = false;
        http_response.fd = open(page.c_str(), O_RDONLY);
        if (http_response.fd > 0)
        {
            struct stat st;
            stat(page.c_str(), &st); // 获取错误页面的属性

            std::string line = "Content-Type : text/html";
            line += LINE_END;
            http_response.response_header.push_back(line);

            line = "Content-Length: ";
            line += std::to_string(st.st_size);
            line += LINE_END;
            http_response.response_header.push_back(line);
            http_request.size = st.st_size;
        }
    }
    void BuildHttpResponseHelper()
    {
        auto &code = http_response.status_code; // 获取前面执行完的状态码，有正确也有错误
        // 构建状态行
        auto &status_line = http_response.status_line; // 状态行的状态码，不一定被填充
        status_line += HTTP_VERSION;                   // 添加http版本
        status_line += " ";
        status_line += std::to_string(code); // 添加状态码
        status_line += " ";
        status_line += Code2Desc(code); // 添加状态码描述，比如Not Found
        status_line += LINE_END;

        // 构建响应正文,可能包括响应报头
        std::string path = WEB_ROOT;
        path += "/";
        switch (code)
        {
        case OK:
            BuildOkResponse();
            break;
        case NOT_FOUND:
            path += PAGE_404;
            HandlerError(path);
            break;
        case BAD_REQUEST:
            path += PAGE_400;
            HandlerError(path);
            break;
        case SERVER_ERROR:
            path += PAGE_500;
            HandlerError(path);
            break;
            //    case 500:
            //      HandlerError(PAGE_500);
            //        break;
        default:
            break;
        }
    }
public:
    void RecvRequest() //读取并解析请求
    {
        //只有当读取请求行和读取请求报头都成功的时候，才执行后续操作
        if((!RecvRequestLine()) && (!RecvRequestHeader())) 
        {
            ParseRequestLine(); //解析请求行
            ParseRequestHeader(); //解析请求报头各字段
            RecvRequestBoby(); //如果是POST请求方法就去读取正文，如果不是POST就什么也不做
        }
    }
    void BuildReponse() //构建http响应
    {
        std::string Path;
        auto &code = http_response.status_code; //状态码
        struct stat st; //用户获取资源文件的属性信息
        int size = 0;
        std::size_t found = 0; //表示文件后缀的点的位置
        if(http_request.method != "GET" && http_request.method != "POST")
        {
            //走到这里说明这是个非法请求（因为我们目前的服务器只支持GET和POST方法）
            std::cout << "method: " << http_request.method << std::endl;
            LOG(WARNING, "method is not right");
            code = BAD_REQUEST;
            goto END;
        }
        if(http_request.method == "GET")
        {
            //如果是GET方法，需要知道http请求行的URL中是否携带了参数
            ssize_t pos = http_request.uri.find('?'); //一般以问号作为分隔符
            if(pos != std::string::npos) //有问号，说明带参，要启动CGI
            {
                Util::CutString(http_request.uri, http_request.path, http_request.query_string, "?");
                http_request.cgi = true;
            }
            else http_request.path = http_request.uri; //没有问号，不启动CGI
        }
        else if(http_request.method == "POST")
        {
            http_request.cgi = true; //如果是POST方法，就要启动CGI机制
            http_request.path = http_request.uri;
        }
        else {} //如果要想支持其它的请求方法都可以在这里往后面扩展

        //测试
        // std::cout << "debug - uri: " << http_request.uri << std::endl;
        // std::cout << "debug - path: " << http_request.path << std::endl;
        // std::cout << "debug - quary_string: " << http_request.query_string << std::endl;
        
        //然后就是需要把客户端传过来的目录，变成我们的web根目录
        Path = http_request.path;
        http_request.path = WEB_ROOT;
        http_request.path += Path;
        
        //std::cout << "debug - path: " << http_request.path << std::endl;
        //如果请求路径以 / 结尾，说明请求的是一个目录
        if(http_request.path[http_request.path.size()-1] == '/')
        {
            http_request.path += HOME_PAGE;
        }
        //当把web目录处理好后，接下来就是要确认要申请的资源是否存在了：
        //1，如果存在，就返回    2，如果不存在，返回首页
        if(stat(http_request.path.c_str(), &st) == 0) //stat函数用于获取某个文件的属性
        {
            //走到这里说明要获取的资源是存在的
            //问题：存在就是可以访问读取的呢？不一定，因为有可能要访问的资源是一个目录
            if(S_ISDIR(st.st_mode))//这是个宏，man手册说是判断st里mode是否为目录
            {
                //走到这里说明申请的资源是一个目录，需要做特殊处理
                http_request.path += "/"; //细节：虽然是一个目录，但是不会以"/"结尾，因为上面已经做了对"/"的处理
                http_request.path += HOME_PAGE;
                stat(http_request.path.c_str(), &st); //细节：由于路径发生更改，所以再重新获取一下属性
            }
            //请求的资源也有可能是一个可执行程序，需要特殊处理
            //当文件的拥有者，所属组，other有任何一个有可执行权限，那么这个文件就是可执行权限
            if( (st.st_mode&S_IXUSR) || (st.st_mode&S_IXGRP) || (st.st_mode&S_IXOTH) )
            {
                http_request.cgi = true;
            }
            http_request.size = st.st_size; //获取目标文件大小，方便后面sendfile发送
        }
        else
        {
            //走到这里说明获取的资源不存在
            LOG(WARNING, http_request.path + " Not Found");
            code = NOT_FOUND;
            goto END;
        }
        //走到了这里，说明没有goto跳转到END，一切正常
        //获得资源后缀
        found = http_request.path.rfind(".");
        if(found == std::string::npos) //没找到后缀，添加默认后缀
        {
            http_request.suffix = ".html";
        }
        else //成功找到后缀
        {
            http_request.suffix = http_request.path.substr(found); //截取点后面的字符
        }

        if(http_request.cgi == true) //启动CGI机制
        {
            code = ProcessCgi(); //CGI处理完后的结果已经存储到:http_response.response_body里面了
        }
        else
        {
            code = ProcessNonCgi(); //以非CGI方式处理：简单的返回静态网页
            //1，目标网页一定是存在的
            //2，返回的不只是网页，还要构建Http响应，将网页以响应正文形式返回
        }
END:
       BuildHttpResponseHelper(); //开始构建http响应报文
    }
    bool SendResponse() //响应构建好，然后就是发送响应了
    {
         //1，先把状态行发过去
        if(send(_sock, http_response.status_line.c_str(), http_response.status_line.size(), 0) <= 0)
            stop = true; //如果发送出错，也用stop标记一下
        //2，再把响应报头发过去
        if(!stop)
        {
            for(auto& iter : http_response.response_header) //是vector，有很多
            {
                if(!stop && send(_sock, iter.c_str(), iter.size(), 0) <= 0)
                    stop = true;
            }
            if(!stop && send(_sock, http_response.blank.c_str(), http_response.blank.size(), 0) <= 0) //状态行和报头发出去后再发一个空行，表示接下来发的就是响应正文了
                stop = true;
            //send发送不是真的发，而是拷贝到Tcp的发送缓冲区
        }
        
        //3，最后发送正文
        if(!stop && http_request.cgi == true) //CGI和非CGI要分开发
        {
            //如果是cgi模式，那么我们的正文是在http_response的body中的
            auto& response_body = http_response.response_body;
            size_t size = 0;
            size_t total = 0;
            const char *start = response_body.c_str();
            while( total < response_body.size() && (size = send(_sock, start + total, response_body.size() - total, 0)) > 0)
            {
                total += size;
            }
            //这个步骤和我们上面把cgi程序执行完之后把数据写回管道那里的步骤是一样的，只是write变成了send
        }
        else
        {
            //正常页面和错误页面通过同一种方法返回
            if(!stop)
            {
                if(sendfile(_sock, http_response.fd, nullptr, http_request.size) <= 0) //发送效率比write和send高一些
                    stop = true;
            }
            close(http_response.fd);
        }
        return stop;
    }
    EndPoint(int sock)
        : _sock(sock)
        , stop(false) 
    {}
    bool IsStop() { return stop; }
    ~EndPoint()
    {
        close(_sock);
    }

private:
    int total;
    int _sock; //通信套接字
    Request http_request; //http请求
    Response http_response; //http响应
    bool stop; //表示读取是否出错
};

//#define DEBUG 1
class CallBack
{
public:
    CallBack()
    {}
    ~CallBack()
    {}
    void operator()(int sock)
    {
        HandlerRequest(sock); //仿函数，重载()，调用HandlerRequest
    }
    static void* HandlerRequest(int sock)
    {
        LOG(INFO, "Hander Request Begin");
#ifdef DEBUG //测试打印http请求报头
        char buffer [4096];
        recv(sock, buffer, sizeof(buffer), 0);
        std::cout << "-------------begin----------------" << std::endl;
        std::cout << buffer << std::endl;
        std::cout << "-------------end----------------" << std::endl;
#else
        EndPoint* ep = new EndPoint(sock);
        ep->RecvRequest();//读取并解析请求
        if(!ep->IsStop()) //当读取解析请求都成功时，才开始构建和发回相应
        {
            LOG(INFO, "Recv No Error, Begin Build And Send Reponse");
            ep->BuildReponse(); //构建响应
            if(ep->SendResponse()) //发回响应
            {
                LOG(WARNING, "Send Error");
            }
        }
        else
        {
            LOG(WARNING, "Recv Error, Stop Build And Send Reponse");
        }
        delete ep;
#endif
        LOG(INFO, "Hander Request End");
    }
};

//测试
// class Entrance
// {
// public:
//     static void* HandlerRequest(void* _sock)
//     {
//         int sock = *(int*)_sock;
//         delete (int*)_sock;
// #ifdef DEBUG //测试打印http请求报头
//     char buffer [4096];
//     recv(sock, buffer, sizeof(buffer), 0);
//     std::cout << "-------------begin----------------" << std::endl;
//     std::cout << buffer << std::endl;
//     std::cout << "-------------end----------------" << std::endl;
// #else
//     EndPoint* ep = new EndPoint(sock);
//     ep->RcvRequest();//读取并解析请求
//     if(!ep->IsStop()) //当读取解析请求都成功时，才开始构建和发回相应
//     {
//         LOG(INFO, "Recv No Error, Begin Build And Send Reponse");
//         ep->BuildReponse(); //构建响应
//         ep->SendResponse(); //发回响应
//     }
//     else
//     {
//         LOG(WARNING, "Recv Error, Stop Build And Send Reponse");
//     }
//     delete ep;
// #endif
//         return nullptr;
//     }
// };